using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.Linq;
using System.Windows.Markup;
using System.Xaml;

using Supremacy.Annotations;
using Supremacy.Collections;
using Supremacy.Resources;
using Supremacy.Types;
using Supremacy.Utility;
using Supremacy.VFS;

namespace Supremacy.Text
{
    [ContentProperty("Groups")]
    public sealed class LocalizedTextDatabase : SupportInitializeBase
    {
        private readonly LocalizedTextGroupCollection _groups;

        public LocalizedTextDatabase()
        {
            _groups = new LocalizedTextGroupCollection();
        }

        public LocalizedTextGroupCollection Groups
        {
            get { return _groups; }
        }

        public void Merge([NotNull] LocalizedTextDatabase database, bool overwrite = false)
        {
            if (database == null)
                throw new ArgumentNullException("database");

            foreach (var group in database.Groups)
                Merge(group, overwrite);
        }

        public void Merge([NotNull] LocalizedTextGroup group, bool overwrite = false)
        {
            if (group == null)
                throw new ArgumentNullException("group");

            LocalizedTextGroup existingGroup;

            if (_groups.TryGetValue(group.Key, out existingGroup))
            {
                foreach (var entry in group.Entries)
                {
                    LocalizedString existingEntry;

                    if (existingGroup.Entries.TryGetValue(entry.Name, out existingEntry))
                        existingEntry.Merge(entry, overwrite);
                    else
                        existingGroup.Entries.Add(entry);
                }
            }
            else
            {
                _groups.Add(group);
            }
        }

        public static IEnumerable<Uri> LocateTextFiles()
        {
            var vfsService = ResourceManager.VfsService;
            if (vfsService == null)
                return Enumerable.Empty<Uri>();

            return LocateTextFiles(vfsService);
        }

        private static IEnumerable<Uri> LocateTextFiles(IVfsService vfsService)
        {
            var files = new List<Uri>();
            
            var fileNames =
                (
                    from source in vfsService.Sources
                    from fileName in source.GetFiles(@"Resources\Text", true, "*.xaml")
                    select fileName
                ).Distinct();

            files.AddRange(fileNames.Select(ResourceManager.GetResourceUri));

            return files;
        }

        private static LocalizedTextDatabase _instance;

        public static LocalizedTextDatabase Instance
        {
            get
            {
                if (_instance == null)
                    _instance = Load();
                return _instance;
            }
        }

        public static LocalizedTextDatabase Load()
        {
            var vfsService = ResourceManager.VfsService;

            var files = LocateTextFiles(vfsService);
            var database = new LocalizedTextDatabase();

            foreach (var file in files)
            {
                try
                {
                    IVirtualFileInfo fileInfo;

                    if (!vfsService.TryGetFileInfo(file, out fileInfo))
                        continue;

                    object content;

                    using (var stream = fileInfo.OpenRead())
                    {
                        content = XamlServices.Load(stream);
                    }

                    var databaseContent = content as LocalizedTextDatabase;
                    if (databaseContent != null)
                    {
                        database.Merge(databaseContent);
                        continue;
                    }

                    var textGroup = content as LocalizedTextGroup;
                    if (textGroup != null)
                    {
                        database.Merge(textGroup);
                        continue;
                    }

                    GameLog.Client.GameData.WarnFormat(
                        "Could not determine the content type of text file '{0}'.  " +
                        "The file will be ignored.",
                        file);
                }
                catch (Exception e)
                {
                    GameLog.Client.GameData.Error(
                        string.Format(
                            "An error occurred while loading localized text file '{0}'.",
                            file),
                        e);
                }
            }

            return database;
        }
    }

    public sealed class LocalizedTextGroupCollection : KeyedCollectionBase<object, LocalizedTextGroup>
    {
        public LocalizedTextGroupCollection()
            : base(o => o.Key) {}
    }

    public static class LocalizedTextGroups
    {
        private static readonly object _galaxyScreen = new StandardLocalizedTextGroupKey("GalaxyScreen");
        private static readonly object _colonyScreen = new StandardLocalizedTextGroupKey("ColonyScreen");

        public static object GalaxyScreen
        {
            get { return _galaxyScreen; }
        }

        public static object ColonyScreen
        {
            get { return _colonyScreen; }
        }

        [TypeConverter(typeof(LocalizedTextGroupKeyConverter))]
        public sealed class StandardLocalizedTextGroupKey
        {
            private readonly string _name;

            internal StandardLocalizedTextGroupKey([NotNull] string name)
            {
                if (name == null)
                    throw new ArgumentNullException("name");

                _name = name;
            }

            public string Name
            {
                get { return _name; }
            }
        }

        internal class LocalizedTextGroupKeyConverter : TypeConverter
        {
            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            {
                if (destinationType == typeof(MarkupExtension))
                    return true;
                return base.CanConvertTo(context, destinationType);
            }

            public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
            {
                var standardKey = value as LocalizedTextGroups.StandardLocalizedTextGroupKey;
                if (standardKey != null &&
                    destinationType == typeof(MarkupExtension))
                {
                    var serializerContext = context as IValueSerializerContext;
                    if (serializerContext != null)
                    {
                        var typeSerializer = serializerContext.GetValueSerializerFor(typeof(Type));
                        if (typeSerializer != null)
                        {
                            return new StaticExtension(
                                typeSerializer.ConvertToString(typeof(LocalizedTextGroups), serializerContext) +
                                "." +
                                standardKey.Name);
                        }
                    }
                    return new StaticExtension
                           {
                               MemberType = typeof(LocalizedTextGroups),
                               Member = standardKey.Name
                           };
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    }

    [Serializable]
    [ContentProperty("Entries")]
    public sealed class LocalizedTextGroup : SupportInitializeBase
    {
        private object _key;
        private readonly LocalizedStringCollection _entries;

        public LocalizedTextGroup()
        {
            _entries = new LocalizedStringCollection();
        }

        public object Key
        {
            get { return _key; }
            set
            {
                VerifyNotInitialized();
                _key = value;
                _entries.GroupKey = _key;
            }
        }

        public LocalizedStringCollection Entries
        {
            get { return _entries; }
        }

        public LocalizedString DefaultEntry
        {
            get
            {
                if (_entries.Count == 0)
                    return null;

                return _entries[0];
            }
        }

        public string DefaultLocalText
        {
            get
            {
                var defaultEntry = this.DefaultEntry;
                if (defaultEntry == null)
                    return null;

                return defaultEntry.LocalText;
            }
        }
    }

    [Serializable]
    public sealed class LocalizedStringCollection : KeyedCollectionBase<string, LocalizedString>,
                                                    ISupportInitializeNotification
    {
        private object _groupKey;

        public LocalizedStringCollection()
            : base(o => o.Name) { }

        internal object GroupKey
        {
            // ReSharper disable MemberCanBePrivate.Local
            get { return _groupKey; }
            set
            {
                VerifyNotInitialized();
                _groupKey = value;
            }
            // ReSharper restore MemberCanBePrivate.Local
        }

        protected override void OnKeyCollision(string key, LocalizedString item)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                GameLog.Client.GameData.WarnFormat(
                    "Localized text group '{0}' has more than one default entry defined." +
                    FormatGroupKey());
            }
            else
            {
                GameLog.Client.GameData.WarnFormat(
                    "Localized text group '{0}' already contains entry '{1}'.",
                    FormatGroupKey(),
                    key);
            }
        }

        private string FormatGroupKey()
        {
            object groupKey = GroupKey;
            if (groupKey == null)
                return string.Empty;

            var typeKey = groupKey as Type;
            if (typeKey != null)
                return string.Format("{{{0}}}", typeKey.Name);

            var nameTypeKey = groupKey as NameTypeTextGroupKey;
            if (nameTypeKey != null)
                return string.Format("{{{0}, {1}}}", nameTypeKey.Type.Name, nameTypeKey.Name);

            return string.Format("{0}", groupKey);
        }

        private bool _isInitialized;

        private void VerifyNotInitialized()
        {
            if (!_isInitialized)
                return;

            throw new InvalidOperationException(SR.InvalidOperationException_AlreadyInitialized);
        }

        #region Implementation of ISupportInitialize
        public void BeginInit()
        {
            lock (SyncRoot)
            {
                _isInitialized = false;
            }
        }

        public void EndInit()
        {
            lock (SyncRoot)
            {
                if (_isInitialized)
                    return;

                if (_groupKey == null)
                    throw new InvalidOperationException("Localized text groups must have a key defined.");

                _isInitialized = true;

                OnInitialized();
            }
        }
        #endregion

        #region Implementation of ISupportInitializeNotification
        public bool IsInitialized
        {
            get
            {
                lock (SyncRoot)
                    return _isInitialized;
            }
        }

        [field: NonSerialized]
        public event EventHandler Initialized;

        private void OnInitialized()
        {
            EventHandler handler = Initialized;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
        #endregion
    }
    
    public sealed class NameTypeTextGroupKey
    {
        private readonly Type _type;
        private readonly string _name;

        public NameTypeTextGroupKey([NotNull] Type type, [NotNull] string name)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (name == null)
                throw new ArgumentNullException("name");

            _type = type;
            _name = name;
        }

        public Type Type
        {
            get { return _type; }
        }

        public string Name
        {
            get { return _name; }
        }
    }

/*
    [TypeConverter(typeof(LocalizedTextEntryConverter))]
    public sealed class LocalizedTextEntry
    {
        private readonly string _name = string.Empty;
        private readonly string _value;

        public LocalizedTextEntry(string name, string value)
        {
            _name = name;
            _value = value;
        }

        [ConstructorArgument("name")]
        public string Name
        {
            get { return _name; }
        }

        [ConstructorArgument("value")]
        public string Value
        {
            get { return _value; }
        }
    }

    public sealed class LocalizedTextEntryConverter : TypeConverter
    {
        private static readonly Lazy<ConstructorInfo> _constructor = new Lazy<ConstructorInfo>(FindConstructor);

        private static ConstructorInfo FindConstructor()
        {
            return typeof(LocalizedTextEntry).GetConstructor(new[] { typeof(string), typeof(string) });
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor))
                return true;

            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var entry = value as LocalizedTextEntry;
            if (entry != null)
            {
                if (destinationType == typeof(InstanceDescriptor))
                {
                    return new InstanceDescriptor(
                        _constructor.Value,
                        new[] { entry.Name, entry.Value },
                        true);
                }
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
*/
}